using System;
using System.Collections.Generic;
using System.Text;

using FlatRedBall;
using FlatRedBall.Graphics;
using FlatRedBall.Math.Geometry;
using FlatRedBall.Input;

using Microsoft.Xna.Framework;
using Microsoft.Devices;
using Microsoft.Xna.Framework.Audio;

using FlatRedBall.Graphics.Animation;

using Xzipit.Utilities;
using Xzipit.Interfaces;
using Xzipit.Utilities.Gestures;
using Xzipit.Utilities.Touch;

namespace Xzipit.GameObjects
{
    public class Zipper : PositionedObject, IDraggable, IPullable
    {
        #region Track
        private class Track
        {
            #region TrackNode
            private class TrackNode : PositionedObject
            {
                #region Fields
                private List<Sprite> _sprites;
                private Vector3 _pos;
                private List<Circle> _collisions;
                private SoundEffect _zipSound;

                private const float TEXTURE_HEIGHT = 1.20f;
                #endregion

                #region Properties

                public float Alpha
                {
                    get { return _sprites[0].Alpha; }
                    set
                    {
                        foreach (Sprite s in _sprites)
                        {
                            s.Alpha = value;
                        }
                    }
                }

                public float AlphaRate
                {
                    get { return _sprites[0].AlphaRate; }
                    set
                    {
                        foreach (Sprite s in _sprites)
                        {
                            s.AlphaRate = value;
                        }
                    }
                }

                #endregion

                public TrackNode(Vector3 pos)
                {
                    _pos = pos;
                    _sprites = new List<Sprite>();
                    _collisions = new List<Circle>();
                    _zipSound = FlatRedBallServices.Load<SoundEffect>(@"Content\Sound\zipper");
                }

                public TrackNode(Vector3 pos, Vector3 nextPos)
                {
                    _pos = pos;
                    _sprites = new List<Sprite>();
                    _collisions = new List<Circle>();
                    DrawTrack(nextPos);
                    RotateSegment(nextPos);
                    _zipSound = FlatRedBallServices.Load<SoundEffect>(@"Content\Sound\zipper");

                }

                private void DrawTrack(Vector3 endPoint)
                {
                    // calculate the distance between 2 nodes.
                    float realLength = (_pos - endPoint).Length() / TEXTURE_HEIGHT;
                    int length = (int)realLength;

                    // round up always so we get an integer number of segments.
                    if (realLength - length > 0.0f)
                    {
                        length++;
                    }

                    // create a sprite and collision for all segments of the track between this and the previous node.
                    for (int i = 0; i < length; i++)
                    {
                        AnimationChainList unzip = new AnimationChainList();
                        AnimationChain opening = new AnimationChain();

                        opening.Add(new AnimationFrame(@"Content/GameObjects/Player/Track/zipper-closed", 0.06f, "Global"));
                        opening.Add(new AnimationFrame(@"Content/GameObjects/Player/Track/zipper-open", 0.06f, "Global"));
                        unzip.Add(opening);

                        Sprite temp = SpriteManager.AddSprite(unzip);
                        temp.AttachTo(this, false);
                        temp.Animate = false;
                        temp.CurrentFrameIndex = 0;
                        temp.PixelScale();
                        temp.RelativeY = 0.0f - i * (2 * temp.ScaleY);
                        temp.UseAnimationRelativePosition = false;

                        Circle c = ShapeManager.AddCircle();
                        c.AttachTo(temp, false);
                        c.Radius = temp.ScaleX;
                        c.Visible = false;

                        _sprites.Add(temp);
                        _collisions.Add(c);
                    }
                }

                private void RotateSegment(Vector3 endPoint)
                {
                    // Rotates a segment of a track so it is positioned correctly between 2 nodes.
                    float faceAtX = _pos.X - endPoint.X;
                    float faceAtY = _pos.Y - endPoint.Y;

                    float rotation = (float)Math.Atan2(faceAtY, faceAtX) - (float)Math.PI / 2.0f;

                    this.RotationZ = rotation;
                }

                public void Destroy()
                {
                        foreach (Sprite s in _sprites)
                        {
                            SpriteManager.RemoveSprite(s);
                        }

                        foreach (Circle c in _collisions)
                        {
                            ShapeManager.Remove(c);
                        }
                }

                public void CheckCollision(Circle zipPos)
                {
                    // modifies the track image from closed to open.
                    for ( int i = 0; i < _collisions.Count; i++)
                    {
                        if (zipPos.CollideAgainst(_collisions[i]))
                        {
                            if (_sprites[i].CurrentFrameIndex != 1)
                            {
                                _zipSound.PlaySound();
                            }
                            _sprites[i].CurrentFrameIndex = 1;
                        }
                    }
                }

                internal void FadeIn()
                {
                    foreach (Sprite s in _sprites)
                    {
                        s.AlphaRate = GameProperties.ALPHA_RATE;
                        if (s.Alpha > 1.0f)
                        {
                            s.AlphaRate = 0.0f;
                            s.Alpha = 1.0f;
                        }
                    }
                }

                internal void QuickFadeIn()
                {
                    foreach (Sprite s in _sprites)
                    {
                        s.Alpha = 1.0f;
                    }
                }
            }

            #endregion

            #region Fields
            private Queue<Vector3> _track;
            private Vector3 _next;
            private int _nodeNum;
            private List<TrackNode> _trackRep;

            #endregion

            #region Constants
            private const float TRACKZ = -.01f;
            #endregion

            #region Properties
            public Vector3 Next
            {
                get { return _next; }
            }

            public bool AtEnd(Circle zipCollision)
            {
                // detects if the zipper is at the last node.
                return _track.Count == 0 && zipCollision.IsPointInside(ref _next);
            }

            public float Alpha
            {
                get { return _trackRep[0].Alpha; }
                set
                {
                    foreach (TrackNode t in _trackRep)
                    {
                        t.Alpha = value;
                    }
                }
            }

            public float AlphaRate
            {
                get { return _trackRep[0].AlphaRate; }
                set
                {
                    foreach (TrackNode t in _trackRep)
                    {
                        t.AlphaRate = value;
                    }
                }
            }

            #endregion

            #region Constructors
            public Track(string contentManagerName, List<Vector3> nodes)
            {
                _track = new Queue<Vector3>();
                _trackRep = new List<TrackNode>();

                _nodeNum = 0;

                // Create a track based on a group of nodes provided by a level file.
                for (int i = 0; i < nodes.Count; i++)
                {
                    if (i != nodes.Count - 1)
                    {
                        TrackNode t = new TrackNode(nodes[i], nodes[i + 1]);
                        t.Position = nodes[i];
                        t.Z = TRACKZ;
                        _trackRep.Add(t);
                    }
                    else
                    {
                        TrackNode t = new TrackNode(nodes[i]);
                        t.Position = nodes[i];
                        t.Z = TRACKZ;
                        _trackRep.Add(t);                     
                    }
                    _track.Enqueue(nodes[i]);
                }

                _next = _track.Dequeue();
            }

            #endregion

            #region Update
            public void CheckCollide(Circle zipCollision)
            {
                // Check to see if the zipper has collided with the next node in the track.
                if (zipCollision.IsPointInside(ref _next) && _track.Count > 0)
                {
                    _next = _track.Dequeue();
                    _nodeNum++;
                }

                // Modify the track image if the Zipper has passed over it.
                foreach (TrackNode t in _trackRep)
                {
                    t.CheckCollision(zipCollision);
                }
            }

            public bool CheckDoneAnimation()
            {
                bool done = false;
                for (int i = 0; i < _trackRep.Count - 1; i++ )
                {
                    done = _trackRep[i].Alpha == 1.0f;
                }

                return done;
            }

            public void FadeIn()
            {
                foreach (TrackNode t in _trackRep)
                {
                    t.QuickFadeIn();
                }
            }

            #endregion

            #region Destroy
            public void Destroy()
            {
                foreach (TrackNode t in _trackRep)
                {
                    t.Destroy();
                }
            }

            #endregion

            internal void QuickFadeIn()
            {
                foreach (TrackNode t in _trackRep)
                {
                    t.QuickFadeIn();
                }
            }
        }
        #endregion

        #region Fields

        public enum ZipperState
        {
            Zipping,
            Jammed,
            Idle
        }

        private ZipperState _state;
        private Sprite _bodySprite;
        private Circle _bodyCollision;
        private Sprite _tabSprite;
        private Circle _tabCollision;

        private Track _track;
        private GameGesture _lastDrag;

        private bool _partialFade;

        // Keep the ContentManager for easy access:
        string _contentManagerName;

        private const float SPEED = 23.0f;
        private const float MAX_DISTANCE = 13.0f;

        #endregion

        #region Properties

        public Circle Collision
        {
            get { return _bodyCollision; }
        }

        public GameGesture LastDrag
        {
            get { return _lastDrag; }
            set
            {
                _lastDrag = value;
                TouchManager.ZipperDrag = _lastDrag;
            }
        }

        public ZipperState State
        {
            get { return _state; }
            set { _state = value; }
        }

        public float Alpha
        {
            get { return _bodySprite.Alpha; }
            set
            {
                _bodySprite.Alpha = value;
                _tabSprite.Alpha = value;
                _track.Alpha = value;
            }
        }

        public float AlphaRate
        {
            get { return _bodySprite.AlphaRate; }
            set
            {
                _bodySprite.AlphaRate = value;
                _tabSprite.AlphaRate = value;
                _track.AlphaRate = value;
            }
        }

        public bool IsAtEnd
        {
            get { return _track.AtEnd(Collision); }
        }

        #endregion

        #region Methods

        #region Constructors
        public Zipper()
        {

        }

        public Zipper(string contentManagerName)
        {
            // Set the ContentManagerName and call Initialize:
            _contentManagerName = contentManagerName;

            // If you don't want to add to managers, make an overriding constructor
            Initialize(true);
        }

        public Zipper(string contentManagerName, List<Vector3> track)
        {
            // Set the ContentManagerName and call Initialize:
            _contentManagerName = contentManagerName;
            _track = new Track(_contentManagerName, track);

            // If you don't want to add to managers, make an overriding constructor
            Initialize(true);
        }

        #endregion

        #region Initialization
        protected virtual void Initialize(bool addToManagers)
        {
            // Here you can preload any content you will be using
            // like .scnx files or texture files.

            _partialFade = false;
            State = ZipperState.Idle;

            if (addToManagers)
            {
                AddToManagers(null);
            }
        }

        public virtual void AddToManagers(Layer layerToAddTo)
        {
            SpriteManager.AddPositionedObject(this);

            // Initialize all sprites and collisions of the zipper.

            // The part of the zipper that interacts with the zipper track.
            _bodySprite = SpriteManager.AddSprite(@"Content\GameObjects\Player\zipper-body", _contentManagerName);
            _bodySprite.AttachTo(this, false);
            _bodySprite.PixelScale();
            _bodySprite.RelativeZ = 0.12f;

            // The collision that is used to tell whether the zipper ran into an Obstacle.
            _bodyCollision = ShapeManager.AddCircle();
            _bodyCollision.AttachTo(_bodySprite, false);
            _bodyCollision.Radius = _bodySprite.ScaleX * 0.55f;
            _bodyCollision.Visible = false;

            // The tab of the zipper that the player uses to move the zipper.
            _tabSprite = SpriteManager.AddSprite(@"Content\GameObjects\Player\zipper-tab", _contentManagerName);
            _tabSprite.AttachTo(this, false);
            _tabSprite.RelativeZ = 0.12f;

            _tabSprite.PixelScale();

            // The collision that is used to tell whether the player is interacting with
            // the zipper
            _tabCollision = ShapeManager.AddCircle();
            _tabCollision.AttachTo(_tabSprite, false);
            _tabCollision.Radius = _tabSprite.ScaleX * 1.5f;
            _tabCollision.RelativeY = -1.2f;
            _tabCollision.Visible = false;

            this.Position = _track.Next;

            ResetGesture();

            TouchManager.AddDraggable(this);
            TouchManager.AddPullable(this);
        }

        #endregion

        #region Update

        public virtual void Activity()
        {
            if (State == ZipperState.Idle)
            {
                ToIdle();
            }
            else if (State == ZipperState.Jammed)
            {
                ToJam();
            }

            if (_lastDrag.GameGestureType != GameGestureType.Drag)
            {
                this.Velocity = Vector3.Zero;
            }

            State = ZipperState.Idle;

        }

        #endregion

        #region Destroy

        public virtual void Destroy()
        {
            _track.Destroy();

            // Remove self from the SpriteManager:
            SpriteManager.RemovePositionedObject(this);

            // Remove any other objects you've created:
            SpriteManager.RemoveSprite(_bodySprite);
            ShapeManager.Remove(_bodyCollision);

            SpriteManager.RemoveSprite(_tabSprite);
            ShapeManager.Remove(_tabCollision);

            TouchManager.RemoveDraggable(this);
            TouchManager.RemovePullable(this);
        }

        public void Stop()
        {
            this.Velocity = Vector3.Zero;
            _tabSprite.RelativeRotationZVelocity = 0.0f;
        }

        #endregion

        #region Fade

        public virtual void FadeOut()
        {
            this.AlphaRate = -GameProperties.ALPHA_RATE;
            _partialFade = false;
        }

        public virtual void PartialFadeOut()
        {
            this.AlphaRate = -GameProperties.ALPHA_RATE;
            _partialFade = true;
        }

        public virtual void FadeIn()
        {
            this.AlphaRate = GameProperties.ALPHA_RATE;
            _track.FadeIn();
        }

        public virtual bool DoneFading()
        {
            if (this.Alpha < 1.0f && this.AlphaRate == GameProperties.ALPHA_RATE)
            {
                _track.CheckDoneAnimation();
                return false;
            }
            else if (this.Alpha >= 1.0f)
            {
                this.Alpha = 1.0f;
                this.AlphaRate = 0.0f;
                return true;
            }
            else if (_partialFade
                && this.Alpha <= .15f && this.AlphaRate == -GameProperties.ALPHA_RATE)
            {
                this.Alpha = .15f;
                this.AlphaRate = 0.0f;
                return true;
            }
            else if (!_partialFade
                && this.Alpha <= 0.0f && this.AlphaRate == -GameProperties.ALPHA_RATE)
            {
                this.Alpha = 0.0f;
                this.AlphaRate = 0.0f;
                return true;
            }
            else
            {
                return false;
            }
        }

        internal void QuickFadeIn()
        {
            this.Alpha = 1.0f;
            this.AlphaRate = 0.0f;

            _track.QuickFadeIn();
        }

        #endregion

        #region Private Methods

        #region Jamming

        private void Unjam()
        {
            State = ZipperState.Idle;
        }

        #endregion

        #region Movement

        private void Zip(Vector3 touchPoint)
        {
            // Find the location the user tapped relative to the current location.
            Vector3 zipVec = touchPoint - this.Position;
            RotateTab(this.Position - touchPoint);

            // Create a vector that finds the direction between the Zipper position and the
            // next node in the Track.
            Vector3 forwardVec = _track.Next - this.Position;

            // Create a scalar that determines how fast the zipper will move toward the
            // next node based on the location the player touched.
            float fScalar = Vector3.Dot(zipVec, forwardVec) / Vector3.Dot(forwardVec, forwardVec);

            // Take the max(fScalar, 0.0f)
            fScalar = fScalar > 0.0f ? fScalar : 0.0f;

            // Detects the winning condition.
            if (_track.AtEnd(_bodyCollision))
            {
                forwardVec = Vector3.Zero;
                State = ZipperState.Idle;
            }

            // Creates a movement vector.
            forwardVec *= fScalar;

            if (forwardVec.Length() > 0.0f)
            {
                // adjust the speed of the zipper so it isn't super fast.
                forwardVec.Normalize();
                forwardVec *= SPEED;
            }
            else
            {
                // make sure the forward vector is never of length less than zero.
                forwardVec = Vector3.Zero;
            }

            this.Velocity = forwardVec;

            // set the state to the zipping state
            this.State = ZipperState.Zipping;

            // see if the zipper opened a new segment of the track
            _track.CheckCollide(_bodyCollision);
        }

        private void RotateTab(Vector3 rotateTowards)
        {
            // Rotates the tab relative to the location the player is touching.
            float faceAtX = rotateTowards.X;
            float faceAtY = rotateTowards.Y;
            //float faceAtX = this.X;// -GestureManager.CurTouchPoint.X;
            //float faceAtY = this.Y;// -GestureManager.CurTouchPoint.Y;

            float rotation = (float)Math.Atan2(faceAtY, faceAtX) - (float)Math.PI / 2.0f;

            _tabSprite.RelativeRotationZ = rotation;

            faceAtX = this.X - _track.Next.X;
            faceAtY = this.Y - _track.Next.Y;

            rotation = (float)Math.Atan2(faceAtY, faceAtX) - (float)Math.PI / 2.0f;

            _bodySprite.RelativeRotationZ = rotation;
        }

        private void ToIdle()
        {
            this.Velocity = Vector3.Zero;
            State = ZipperState.Idle;
            ResetGesture();
        }

        private void ToJam()
        {
            this.Velocity = Vector3.Zero;
            State = ZipperState.Jammed;
            ResetGesture();
        }
        
        #endregion

        #region Gesture Management

        private void ResetGesture()
        {
            LastDrag = new GameGesture(GameGestureType.None, Vector3.Zero, Vector3.Zero, 0, -2);
            this.Velocity = Vector3.Zero;
        }

        #region Drag
        public bool WasDragged(GameGesture g)
        {
            
            return (g.GameGestureType == GameGestureType.Drag
                && (_tabCollision.IsPointInside(g.End.X, g.End.Y)
                || g.Id == _lastDrag.Id)
                && !IsAtEnd);
        }

        public bool OnDrag(GameGesture g)
        {
            if (State != ZipperState.Jammed)
            {
                Vector3 touchPoint = g.End;
                Zip(touchPoint);

                LastDrag = g;

                _track.CheckCollide(_bodyCollision);

                _state = ZipperState.Zipping;

                return true;
            }
            else
            {
                ToJam();
                return false;
            }
        }

        #endregion

        #region Pull

        public bool WasPulled(GameGesture g)
        {
            return ((g.Id == _lastDrag.Id
                && g.GameGestureType == GameGestureType.Pull
                && !IsAtEnd));
        }

        public void OnPull(GameGesture g)
        {
            ToIdle();
        }

        #endregion

        #endregion

        #endregion

        #endregion
    }
}
